package cn.virens.common.components.bytes;

import cn.virens.common.exception.APIException;
import cn.virens.common.utils.number.ByteUtil;

import java.io.Serializable;
import java.nio.ByteBuffer;

/**
 * 环形数据缓冲区
 *
 * @author virens
 */
public class RingByteBuffer implements Serializable {
    private static final long serialVersionUID = 3372057734674057722L;

    private final byte[] bytes;
    private final int capacity;

    private int index;
    private int length;
    private int marker;

    /**
     * 构造环形数据缓冲区
     *
     * @param capacity 缓冲池大小
     */
    public RingByteBuffer(int capacity) {
        this.bytes = new byte[capacity];
        this.capacity = capacity;
    }

    /**
     * 读一位byte值
     *
     * @return 返回值
     * @throws APIException 无
     */
    public synchronized byte read() throws APIException {
        if (length == 0 || length < 1) return -1;
        byte answer = this.bytes[index];

        // 将当前下标偏移1个字节
        this.length = len(length - 1);
        this.index = idx(index + 1);

        // 返回当前字节
        return answer;
    }

    public synchronized byte read(int offset) throws APIException {
        if (length == 0 || length > capacity) return -1;
        if (0 > offset || offset >= length) return -1;

        // 返回指定字节
        return bytes[idx(index + offset)];
    }

    public synchronized short readShort() throws APIException {
        if (length == 0 || length < 2) return -1;

        byte byte1 = this.bytes[idx(index + 0)];
        byte byte2 = this.bytes[idx(index + 1)];

        // 将当前下标偏移2个字节
        this.length = len(length - 2);
        this.index = idx(index + 2);

        // 返回当前字节
        return (short) (byte1 << 8 | byte2);
    }

    public synchronized short readShort(int offset) throws APIException {
        if (0 >= offset || offset > length) return -1;
        if (length == 0 || length > capacity) return -1;

        byte byte1 = this.bytes[idx(index + offset + 0)];
        byte byte2 = this.bytes[idx(index + offset + 1)];

        // 返回当前字节
        return (short) (byte1 << 8 | byte2);
    }

    public synchronized int reset(int offset) {
        // 计算marker点与当前下标偏移数值
        int readLen = index - marker;
        if (readLen < 0) {
            readLen = readLen + capacity;
        }

        // 计算原始数据长度
        int origLen = length + readLen;

        // 恢复标注点的下标及数据长度
        this.length = len(origLen - offset);
        this.index = idx(marker + offset);

        // 返回当前下标
        return index;
    }

    public synchronized byte[] array() throws APIException {
        byte[] answer = new byte[length];

        int indexEnd = index;
        for (int i = 0; i < length; i++) {
            answer[i] = this.bytes[idx(indexEnd++)];
        }

        return answer;
    }

    public synchronized void write(byte data) throws APIException {
        this.bytes[idx((index + length))] = data;

        // 判断增加一个字节后数据长度是否溢出
        if (this.length + 1 > capacity) {
            // 长度溢出，起点加1，长度不变
            this.index = (index + 1) % capacity;
            this.length = capacity;
        } else {
            this.length = length + 1;
        }
    }

    public synchronized void write(byte[] data) throws APIException {
        int indexStart = Math.max(data.length - capacity, 0);
        int indexEnd = (index + length) % capacity;

        // 只要传入数据中从后开始长度为size部分
        for (int i = indexStart; i < data.length; i++) {
            this.bytes[idx(indexEnd++)] = data[i];
        }

        // 计算数据长度
        this.length = Math.min(length + data.length, capacity);

        // 根据最后一个字节下标减掉数据长度计算下标起点
        int indexVal = indexEnd - length;
        if (indexVal < 0) {
            indexVal = indexVal + capacity;
        }

        // 赋值下标位置（防止溢出）
        this.index = indexVal % capacity;
    }

    public synchronized void write(ByteBuffer data) throws APIException {
        int remaining = data.remaining();
        int indexEnd = (index + length) % capacity;
        int indexStart = Math.max(remaining - capacity, 0);

        // 只要传入数据中从后开始长度为size部分
        for (int i = indexStart; i < remaining; i++) {
            this.bytes[idx(indexEnd++)] = data.get();
        }

        // 计算数据长度
        this.length = Math.min(length + remaining, capacity);

        // 根据最后一个字节下标减掉数据长度计算下标起点
        int indexVal = indexEnd - length;
        if (indexVal < 0) {
            indexVal = indexVal + capacity;
        }

        // 赋值下标位置（防止溢出）
        this.index = indexVal % capacity;
    }

    /**
     * 获取可读数据下标
     */
    public int index() {
        return index;
    }

    /**
     * 设置可读数据下标
     */
    public synchronized int index(int offset) throws APIException {
        if (length == 0 || length > capacity) return -1;
        if (0 >= offset || offset > length) return -1;

        // 将当前下标偏移指定数目
        this.length = len(length - offset);
        this.index = idx(index + offset);

        // 返回下标
        return index;
    }

    /**
     * 获取可读数据下标
     */
    public int getIndex() {
        return index;
    }

    /**
     * 设置可读数据下标
     */
    public void setIndex(int index) {
        this.index = index;
    }

    /**
     * 获取可读数据长度
     */
    public int length() {
        return length;
    }

    /**
     * 获取可读数据长度
     */
    public int getLength() {
        return length;
    }

    /**
     * 设置可读数据长度
     */
    public void setLength(int length) {
        this.length = length;
    }

    /**
     * 设置标注点下标
     */
    public int marker() throws APIException {
        if (length == 0) return -1;

        // 将当前下标记入标记点
        this.marker = index;

        // 返回标记点
        return marker;
    }

    /**
     * 获取标记点下标
     */
    public int getMarker() {
        return marker;
    }

    /**
     * 设置标记点下标
     */
    public void setMarker(int marker) {
        this.marker = marker;
    }

    @Override
    public String toString() {
        return ByteUtil.toHex(array());
    }

    /**
     * 计算下标
     */
    protected int idx(int index) {
        return index % capacity;
    }

    /**
     * 计算长度
     */
    protected int len(int length) {
        if (length > capacity) {
            return capacity;//
        }

        return length;
    }
}
